#include <tty/console.h>
#include <tty/tty.h>
#include <common.h>
#include <mm/valloc.h>
#include <string.h>
#include <mm/mm.h>
#include <device/device.h>
#include <status.h>
#include <fs.h>
#include <timer.h>

static console_t console_;

static int tty_write(device_t* dev, void* buf, size_t offset, size_t len) {
    console_t* console = (console_t*)dev->impl;
    console_write(console, buf, len);
    return len;
}

static int tty_read(struct device* dev, void* buf, size_t offset, size_t len) {
    return ENOSET;
}

void console_register() {
    device_t* tty = device_add(NULL, &console_, "tty", 3, FS_CHAR);
    tty->read = tty_read;
    tty->write = tty_write;

    timer_create(25, TIMER_PERIODIC, console_flush, NULL);
}

void console_init() {
    memset(&console_, 0, sizeof(console_t));
    rbuffer_init(&console_.out, valloc(8192), 8192, 0);
    rbuffer_init(&console_.in, valloc(4096), 4096, 0);
}

static size_t find_next_line(size_t start) {
    rbuffer_t* out = &console_.out;
    while (start != out->pwrite && ((char*)out->data)[start] != '\n') {
        start = (start + 1) % out->size;
    }
    return start + 1;
}

void console_flush() {
    if (mutex_locked(&console_.out.lock)) {
        return;
    }
    if (!(console_.out.flags & RBUF_DIRTY)) {
        return;
    }

    tty_flush_buffer(&console_.out);

    console_.out.flags &= ~RBUF_DIRTY;
}

void console_write_str(char* s) {
    console_write(&console_, s, strlen(s));
}

void console_write_char(char c) {
    console_write(&console_, &c, 1);
}

void console_scroll_up() {
    rbuffer_t* out = &console_.out;
    mutex_lock(&out->lock);
    size_t pos = console_.windos_start - 2; // -1 for \n, -1 for \r
    while (pos < console_.windos_start && pos != out->pwrite && ((char*)out->data)[pos] != '\n') {
        pos--;
    }
    pos++;

    if (pos > console_.windos_start) pos = 0;

    out->flags |= RBUF_DIRTY;
    console_.windos_start = pos;
    mutex_unlock(&out->lock);
}

void console_scroll_down() {
    rbuffer_t* out = &console_.out;
    mutex_lock(&out->lock);

    console_.windos_start = find_next_line(out->pread);
    out->flags |= RBUF_DIRTY;
    mutex_unlock(&out->lock);
}

void console_write(console_t* con, char* data, size_t len) {
    rbuffer_t* out = &con->out;
    mutex_lock(&out->lock);
    rbuffer_set_rptr(out, con->windos_start);

    char* buf = out->data;
    size_t wptr = out->pwrite;
    size_t rptr = out->pread;

    char c;
    for (size_t i = 0; i < len; i++) {
        c = data[i];
        if (!c) {
            continue;
        }
        if (c == '\n') {
            con->lines++;
        } else if (c == '\b') {
            wptr = wptr ? wptr - 1 : out->size - 1;
            continue;
        }
        buf[wptr] = c;
        wptr = (wptr + 1) % out->size;
    }

    rbuffer_set_wptr(out, wptr);

    while (con->lines >= VGA_LINE_NUM) {
        rptr = find_next_line(rptr);
        con->lines--;
    }

    rbuffer_set_rptr(out, rptr);
    con->windos_start = rptr;
    out->flags |= RBUF_DIRTY;
    mutex_unlock(&out->lock);
}